﻿using System.IO;
using System.Collections.Concurrent;

namespace Update
{
    public static class DirectoryCopier
    {
        /// <summary>
        /// 同步拷贝文件夹及其所有内容
        /// </summary>
        /// <param name="sourceDir">源文件夹路径</param>
        /// <param name="destinationDir">目标文件夹路径</param>
        /// <param name="overwrite">是否覆盖已存在的文件</param>
        /// <param name="excludedDirectories">要排除的目录相对路径数组</param>
        /// <param name="excludedFiles">要排除的文件相对路径数组</param>
        public static void CopyDirectory(
            string sourceDir,
            string destinationDir,
            bool overwrite = true,
            string[]? excludedDirectories = null,
            string[]? excludedFiles = null)
        {
            if (string.IsNullOrWhiteSpace(sourceDir))
                throw new ArgumentException("源文件夹路径不能为空", nameof(sourceDir));

            if (string.IsNullOrWhiteSpace(destinationDir))
                throw new ArgumentException("目标文件夹路径不能为空", nameof(destinationDir));

            // 检查源文件夹是否存在
            var sourceDirectory = new DirectoryInfo(sourceDir);
            if (!sourceDirectory.Exists)
                throw new DirectoryNotFoundException($"源文件夹不存在: {sourceDir}");

            // 确保目标文件夹存在
            Directory.CreateDirectory(destinationDir);

            // 处理空排除数组并转换为规范化路径
            var excludedDirsSet = NormalizeExcludedPaths(excludedDirectories, sourceDir);
            var excludedFilesSet = NormalizeExcludedPaths(excludedFiles, sourceDir);

            CopyDirectoryInternal(sourceDirectory, destinationDir, overwrite, excludedDirsSet, excludedFilesSet);
        }

        /// <summary>
        /// 规范化排除路径，确保使用统一的路径格式进行比较
        /// </summary>
        private static HashSet<string> NormalizeExcludedPaths(string[]? paths, string basePath)
        {
            var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            if (paths != null)
            {
                foreach (var path in paths)
                {
                    // 使用 Path.GetFullPath 来规范化路径（处理 ../、./ 等）
                    string fullPath = Path.GetFullPath(Path.Combine(basePath, path));
                    result.Add(fullPath);
                }
            }

            return result;
        }

        /// <summary>
        /// 内部递归拷贝方法
        /// </summary>
        private static void CopyDirectoryInternal(
            DirectoryInfo sourceDirectory,
            string destinationDir,
            bool overwrite,
            HashSet<string> excludedDirectories,
            HashSet<string> excludedFiles)
        {
            // 拷贝所有文件（排除指定文件）
            foreach (var file in sourceDirectory.GetFiles())
            {
                if (excludedFiles.Contains(file.FullName))
                    continue;

                string destFilePath = Path.Combine(destinationDir, file.Name);
                file.CopyTo(destFilePath, overwrite);
            }

            // 递归拷贝所有子文件夹（排除指定目录）
            foreach (var subDir in sourceDirectory.GetDirectories())
            {
                if (excludedDirectories.Contains(subDir.FullName))
                    continue;

                string destSubDir = Path.Combine(destinationDir, subDir.Name);
                Directory.CreateDirectory(destSubDir);
                CopyDirectoryInternal(subDir, destSubDir, overwrite, excludedDirectories, excludedFiles);
            }
        }

        /// <summary>
        /// 异步拷贝文件夹及其所有内容
        /// </summary>
        /// <param name="sourceDir">源文件夹路径</param>
        /// <param name="destinationDir">目标文件夹路径</param>
        /// <param name="overwrite">是否覆盖已存在的文件</param>
        /// <param name="progress">进度报告器</param>
        /// <param name="excludedDirectories">要排除的目录相对路径数组</param>
        /// <param name="excludedFiles">要排除的文件相对路径数组</param>
        public static async Task CopyDirectoryAsync(
            string sourceDir,
            string destinationDir,
            bool overwrite = true,
            IProgress<double>? progress = null,
            string[]? excludedDirectories = null,
            string[]? excludedFiles = null)
        {
            if (string.IsNullOrWhiteSpace(sourceDir))
                throw new ArgumentException("源文件夹路径不能为空", nameof(sourceDir));

            if (string.IsNullOrWhiteSpace(destinationDir))
                throw new ArgumentException("目标文件夹路径不能为空", nameof(destinationDir));

            var sourceDirectory = new DirectoryInfo(sourceDir);
            if (!sourceDirectory.Exists)
                throw new DirectoryNotFoundException($"源文件夹不存在: {sourceDir}");

            // 处理空排除数组并转换为规范化路径
            var excludedDirsSet = NormalizeExcludedPaths(excludedDirectories, sourceDir);
            var excludedFilesSet = NormalizeExcludedPaths(excludedFiles, sourceDir);

            // 获取所有文件用于进度计算（排除指定文件和目录）
            var allFiles = sourceDirectory.GetFiles("*", SearchOption.AllDirectories)
                .Where(file => !excludedFilesSet.Contains(file.FullName) &&
                              !IsDirectoryExcluded(file.Directory, excludedDirsSet))
                .ToArray();

            int totalFiles = allFiles.Length;
            if (totalFiles == 0)
            {
                // 如果没有文件需要拷贝，确保目标目录存在即可
                Directory.CreateDirectory(destinationDir);
                progress?.Report(100);
                return;
            }

            // 使用ConcurrentBag来跟踪进度
            var copiedFiles = new ConcurrentBag<int>();
            var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };

            // 预先创建所有需要的目录结构
            CreateDirectoryStructure(allFiles, sourceDirectory.FullName, destinationDir, excludedDirsSet);

            await Task.Run(() =>
            {
                Parallel.ForEach(allFiles, options, file =>
                {
                    try
                    {
                        string relativePath = GetRelativePath(file.FullName, sourceDirectory.FullName);
                        string destFilePath = Path.Combine(destinationDir, relativePath);

                        file.CopyTo(destFilePath, overwrite);

                        copiedFiles.Add(1);

                        // 更新进度
                        if (progress != null)
                        {
                            double percentage = (double)copiedFiles.Count / totalFiles * 100;
                            progress.Report(percentage);
                        }
                    }
                    catch (Exception ex)
                    {
                        // 记录错误但继续处理其他文件
                        Console.WriteLine($"拷贝文件 {file.FullName} 时出错: {ex.Message}");
                    }
                });
            });

            progress?.Report(100);
        }

        /// <summary>
        /// 获取相对路径（跨平台兼容）
        /// </summary>
        private static string GetRelativePath(string fullPath, string basePath)
        {
            // 确保 basePath 以目录分隔符结尾
            if (!basePath.EndsWith(Path.DirectorySeparatorChar) &&
                !basePath.EndsWith(Path.AltDirectorySeparatorChar))
            {
                basePath += Path.DirectorySeparatorChar;
            }

            Uri fullUri = new Uri(fullPath);
            Uri baseUri = new Uri(basePath);

            return Uri.UnescapeDataString(baseUri.MakeRelativeUri(fullUri).ToString()
                .Replace('/', Path.DirectorySeparatorChar));
        }

        /// <summary>
        /// 检查目录或其父目录是否被排除
        /// </summary>
        private static bool IsDirectoryExcluded(DirectoryInfo? directory, HashSet<string> excludedDirectories)
        {
            var current = directory;
            while (current != null)
            {
                if (excludedDirectories.Contains(current.FullName))
                    return true;
                current = current.Parent;
            }
            return false;
        }

        /// <summary>
        /// 预先创建所有需要的目录结构
        /// </summary>
        private static void CreateDirectoryStructure(FileInfo[] allFiles, string sourceRoot, string destinationRoot, HashSet<string> excludedDirectories)
        {
            var directories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            foreach (var file in allFiles)
            {
                string relativePath = GetRelativePath(file.FullName, sourceRoot);
                string? directoryPath = Path.GetDirectoryName(relativePath);

                if (!string.IsNullOrEmpty(directoryPath))
                {
                    string fullDestPath = Path.Combine(destinationRoot, directoryPath);

                    // 检查源目录是否应该被排除
                    string fullSourceDirPath = Path.Combine(sourceRoot, directoryPath);
                    if (!excludedDirectories.Contains(fullSourceDirPath))
                    {
                        directories.Add(fullDestPath);
                    }
                }
            }

            // 并行创建所有目录
            Parallel.ForEach(directories, dir =>
            {
                Directory.CreateDirectory(dir);
            });
        }
    }
}